home *** CD-ROM | disk | FTP | other *** search
/ TPUG - Toronto PET Users Group / TPUG Users Group CD / TPUG Users Group CD.iso / XTRAS / WINUAE / Docs / README.compemu < prev    next >
Text File  |  2000-12-15  |  22KB  |  419 lines

  1.                         The JIT compiler for UAE
  2.  
  3. Introduction
  4. ============
  5.  
  6. UAE has always been hampered by being somewhat lacking in CPU performance.
  7. Undoubtedly, it was amazingly fast for what it did, but it never really
  8. was an alternative to a decked out, no expense spared Amiga.
  9.  
  10. The Just In Time compiler should help change that.
  11.  
  12. If you just want to know how to *use* it, rather than how it works,
  13. stop right now and read the file README.umisef. Yep, that name is obscure;
  14. I just wanted to make sure you at least look at this one once ;-)
  15.  
  16. Normal Emulation
  17. ================
  18.  
  19. In order to understand how the JIT compiler works, and especially how
  20. it speeds up emulation, one has to first understand how the normal UAE
  21. CPU emulation works. As an example, here is a small 68k routine that
  22. copies a block of memory, from A1 to 0x42000000. 
  23.  
  24.          mov.l 0x42000000,A0
  25. loop:    mov.l (A1+),(A0+)
  26.          cmp.l 0x42010000,A0
  27.          bne   loop
  28.  
  29. The syntax might be a bit off, but you get the idea.
  30.  
  31. The normal emulation works one instruction at a time. It maintains a 
  32. pointer to where the next instruction can be found in the host's memory
  33. (which means you can only execute stuff from "real" memory --- can't
  34. execute the CIA registers, for example ;-).
  35.  
  36. As a first step, the emulation loads the 16 bit value that pointer points
  37. to, i.e. the opcode. The opcode is then used as an index into a lookup
  38. table that contains 65536 pointers to functions; The functions pointed to
  39. are the respective handlers for the instructions matching the opcode.
  40.  
  41. Next comes the actual call of that routine. In order to make life easier
  42. (and UAE a managable size), most handler routines handle more than one
  43. opcode --- for example, our mov.l (A1+),(A0+) would be handled by the
  44. same routine as mov.l (A7+),(A3+). In order to handle all the different
  45. opcodes for which they can be called, most handler routines extract the
  46. numbers of the registers used from the opcode (which gets that passed as
  47. an argument). In order to do so, they have to shift the bits in the 
  48. opcode around a little, and then mask out the needed bits. That then gives
  49. indices into the array that holds the current values for the registers;
  50. If a register is read from or written to, the indices are used to calculate
  51. the actual address to read from or write to.
  52.  
  53. OK, almost done --- one rather small part of emulating an instruction is
  54. actually *doing* it. The mov.l instructions up there basically just realize
  55. that they should store their source in their destination, the cmp.l subtracts
  56. the immediate from the register's value, and the bne looks at the flags and
  57. decides whether or not to change the instruction pointer.
  58.  
  59. Last but not least comes the cleanup --- if the instruction is supposed to
  60. produce flags, these need to be calculated; Also, the instruction pointer
  61. gets moved to the next instruction. Then any changed registers, any
  62. changed flags and changed state information is written back to the 
  63. respective memory locations, the handler routine executes a return, and
  64. the whole cycles starts again.
  65.  
  66. Bottlenecks
  67. ===========
  68.  
  69. This way of doing it has several big performance drawbacks. At the very
  70. start, the instruction fetch and handler lookup look sort of like this:
  71.  
  72.     opcode=*instruction_pointer;
  73.     handler=handlers[opcode];
  74.     call handler;
  75.  
  76. That's two memory accesses before we even reach the "call", and it's a
  77. straight dependency chain --- each instruction depends on the result
  78. of the previous. Hopefully, all those memory locations are in the L1,
  79. but even then, the latency is 3 cycles each time (on a PII), for 6 cycles 
  80. minimum so far.
  81. Another real biggie is the call instruction --- it is always the *same*
  82. instruction, but the target changes around all the time. This means that
  83. the CPU has little hope of ever predicting the target correctly, leading
  84. to a pipeline stall. Uh-oh! Pipeline stalls are *really* expensive on the
  85. PII, to the tune of 10 or so cycles. The total is up to 16.
  86. Most 68k instructions read at least one register, so most handler start
  87. out with a shift, an AND, and then an indexed memory access. Of course,
  88. they depend on each other, so the minimum time for this is another 5
  89. cycles before the register value can be used. Total 21, and we haven't done
  90. a thing yet.
  91. Then most instructions create flags. For many of the most often used
  92. instructions, the x86 instruction that does the calculation will produce
  93. the right flags (and for mov's, an extra TEST x86 instruction will do
  94. the trick). UAE is clever to pick these up --- but isn't clever in the
  95. way it does it. It uses pushf --- which translates in 16 micro-ops and
  96. takes at least 7 cycles. There is a better way (using LAHF and TESTO),
  97. but unless one is very careful, that way can easily lead to a so called
  98. "partial register stall" --- which just means that we get screwed in a 
  99. whole new and interesting way, but screwed we get, for another 7 cycles
  100. or so. We are now up to 28, just for the overhead of a typical instruction.
  101. [ Update: The latest versions of UAE-JIT contains patches that overcome 
  102. some of the flag-handling problems of the normal emulation. By and large,
  103. the above is still true, though]
  104.  
  105. Considering the need to increment the instruction pointer, write back
  106. the registers and flags to memory, and the "ret", and also considering
  107. that the handlers are allowed to clobber any flags they like, and that
  108. thus there is an extra memory read at the very start of the whole thing
  109. (rereading the instruction pointer from memory), the total overhead is
  110. something lik 35 cycles. That easily dwarfs the time it actually takes
  111. to *do* whatever the instrutions are supposed to do (and 35 is assuming
  112. the ideal case, where all the memory accesses are satisfied from L1).
  113.  
  114. There is one more performance killer --- 68k instructions that access memory
  115. (and let's face it, that's most of them ;-). Unfortunately, the 68k uses
  116. memory mapped I/O, so an innocent mov.l (A1+),(A0+) could access just
  117. about *anything*, depending on the values of A1 and A0. This means that
  118. each memory access has to go through several steps --- first, the address
  119. gets shifted 16 bits to the right and the result taken as an index into a
  120. lookup table. That yields a pointer to a structure; In that structure,
  121. the addresses of handler routines for reading and writing bytes, words or
  122. longwords can be found. So one more memory access gets the address of an
  123. appropriate handler, which then gets called (yep, a calculated subroutine
  124. call for every single memory access!). The handlers for "real" memory
  125. then mask the address, add it to the base address for their memory segment,
  126. and perform the requested operation. This takes a *lot* of time. It also
  127. (potentially) clobbers many registers, so that the instruction handler 
  128. routine often has to temporarily store stuff on the stack to keep it
  129. past the memory access.
  130.  
  131. Take all that together, and you get to a pretty constant average of 70 or 
  132. so x86 cycles per emulated m68k instruction. If it wasn't for great fast
  133. x86 chips, this would be really bad. As it stands, it's just annoying.
  134.  
  135.  
  136. Solving the Bottlenecks
  137. =======================
  138.  
  139. Right! Time to solve some of these problems. Let's start at the top:
  140. The 2 memory lookups and the calculated call at the start of emulating
  141. each instruction will always give the exact same results for any given
  142. instruction. So the obvious thing to do is to do it all once, and then
  143. keep the state around.
  144. At this point, the concept of a "block" needs to be introduced. A "block"
  145. is a bunch of consecutive instructions that will *always* execute all
  146. together, one after the other, as long as the first one is executed.
  147. In the example, there are two interesting blocks. Here is the example
  148. again:
  149.  
  150.             mov.l 0x42000000,A0
  151.    loop:    mov.l (A1+),(A0+)
  152.             cmp.l 0x42010000,A0
  153.             bne   loop
  154.  
  155. The first interesting block consists of all 4 instructions. The second
  156. interesting block starts at "loop" and consists of three instructions.
  157. The important thing here is that the "bne loop" instruction definitely
  158. ends a block --- what instruction comes after it depends on the state
  159. of the flags, and thus any "find out once and keep the info" scheme
  160. cannot work it out.
  161. One of the ideas behind the x86 compiler is that it operates on blocks.
  162. So whenever a block ends, we *then* check whether there is some pretranslated
  163. stuff for the block starting with the next instruction. If yes, that
  164. pretranslated stuff (a whole block) is executed. If not, the normal
  165. emulation is done in the normal way until it hits an end-of-block
  166. instruction. This means that for each block, the cache of pretranslated
  167. stuff is only checked once, reducing the extra overhead introduced by
  168. those checks. However, while doing the normal emulation, we also keep
  169. track of what locations the opcodes in the block were fetched from.
  170.  
  171. A simple compiler can do something rather ingenious --- whenever the
  172. normal emulation finishes a block, it "compiles" it into a series of
  173. native x86 instructions along the lines of
  174.  
  175.        mov $opcode1,%eax
  176.        call $handler1
  177.        mov $opcode2,%eax
  178.        call $handler2
  179.        mov $opcode3,%eax
  180.        call $handler3
  181.        
  182. and so on (the opcodes get loaded into %eax so that the handler routines
  183. can shift and mask them like they expect to). This has several advantages:
  184.  
  185.   * The whole get-instruction-pointer-then-fetch-opcode-and-look-up-handler 
  186.     stuff is done once, during compile time, and then is encoded directly 
  187.     in the x86 instruction stream. Saves about 9 cycles per instruction.
  188.   * The calls are no longer calculated, but rather constant, and each
  189.     68k instruction has a call of its own --- which will give the host CPU
  190.     ample opportunity to correctly predict the targets, thus saving another
  191.     10 or so cycles per instruction.
  192.  
  193. "Compiling" blocks in this way (and recognizing blocks in the first place)
  194. creates another opportunity to save a few cycles. In the example code,
  195. the mov.l (A1+),(A0+) is *always* followed by the comparison instruction.
  196. The comparison doesn't use any flags, and overwrites any flags the mov.l
  197. might have set or cleared. In other words, the flags generated by the mov.l
  198. can never make any difference. Because this can be determined during
  199. compilation time, and because the particular mov.l gets compiled into
  200. a particular call, we can call a different handler for it --- one which
  201. does the same thing, but just doesn't bother generating any flags. Of course,
  202. those handlers need to be generated somehow, but that's trivial. And
  203. every time generating the flags can be avoided by this, it save another 
  204. 7 or more cycles.
  205.  
  206. One more problem --- what happens when the 68k overwrites some memory we
  207. have pretranslated? Well, Motorola did the right thing, and didn't fudge
  208. some silly logic into their processors that transparently detects this
  209. situation. Instead, if you want the CPU to acknowledge new code, you need
  210. to explicitly flush the icache. And every time the emulated 68020 does
  211. that, we simply throw away all translations and restart with a blank slate.
  212.  
  213. Implementing these rather simple compilation options will result in 30-100%
  214. speedup, depending on the task.
  215.  
  216.  
  217. Getting more aggressive
  218. =======================
  219.  
  220. Once we have taken that first step, however, and have all the infrastructure
  221. for detecting and handling blocks in place, it becomes soooo much easier
  222. to get a little bit more aggressive, step by step.
  223.  
  224. First, instead of outputting the 
  225.  
  226.       mov $opcode_x,%eax
  227.       call $handler_x
  228.  
  229. sequence, we can directly output code to just do what the instruction is
  230. supposed to do. And we can do this step by step, because if we can't handle
  231. a particular instruction, there is always the simple mov/call pair to fall
  232. back on. Of course, the best choices for opcodes to do this for first are
  233. the opcodes that get executed the most. And because at compile time, we
  234. already know the exact opcode, we can work out what registers get used
  235. *then*, and can simply address them directly (rather than doing the 
  236. shift-mask-and-address_indexed routine of the normal handler routine).
  237. And of course it also saves the call/return overhead, not to mention that
  238. some of the hand-designed x86 code tends to be a fair bit more efficient
  239. than what gcc creates from the portable C code.
  240.  
  241. A word about how to do the translating --- UAE has a wonderful framework
  242. in place to generate the handler routines for the normal emulation. Simply
  243. copying that framework allows us to create translation routines in much
  244. the same way. Of course, instead of actually *doing* stuff, those routines
  245. must themselves *generate code* that will eventually do stuff, and this
  246. extra level of indirection (writing a program which outputs routines which
  247. generate routines which emulate 68k instructions) can be a bit mind-twisting
  248. at times, but it definitely beats the alternatives.
  249.  
  250. And here is another word of hard earned wisdom (aka "Tried it, and after
  251. a few days, had myself thoroughly backed into a corner, so had to scrap
  252. it" ;-): Keep it simple. Don't try to cram too much into one layer,
  253. rather introduce more layers, just as long as you can keep each one
  254. simple and manageable.
  255. In the UAE JIT compiler, the translation routines generated don't actually
  256. contain code to emit x86 code. Instead, they contain lots of calls along the
  257. lines of
  258.  
  259.       mov_l_rr(src,dst);
  260.       cmp_l_ri(dst,1889);
  261.  
  262. and so on. Here is an almost-actual example (I removed some stuff that
  263. we haven't dealt with yet, and formatted more nicely):
  264.  
  265. {
  266.     uae_s32 srcreg = imm8_table[((opcode >> 1) & 7)];
  267.     uae_s32 dstreg = (opcode >> 8) & 7;
  268.         uae_u8 scratchie=S1;
  269.     int src = scratchie++;
  270.     mov_l_ri(src,srcreg);
  271.         {
  272.         int dst=dstreg+8;
  273.         int tmp=scratchie++;
  274.         sign_extend_16_rr(tmp,src);
  275.         add_l(dst,tmp);
  276.         mov_l_rr(dstreg+8,dst);
  277.     }
  278. }
  279.  
  280. That's the code that would get called for  "mov.w 3,a0". The way it works
  281. is like this --- the routine targets an x86-like processor, but one with
  282. 32 registers. The first 16 of these are mapped to D0-D7 and A0-A7; The
  283. next three hold flags and the instruction pointer, and then there are 13
  284. scratch registers, S1 to S13. First, the routine works out what register
  285. and immediate to use. It then calls mov_l_ri(), which will generate code
  286. to move the immediate into a register (in this case, a scratch register).
  287. That value then gets sign extended (pointless in this case, but that's 
  288. what you get for generating these routines with a program...) into yet
  289. another scratch register, then adds the immediate to the register, and
  290. moves the result back into the register. But all the translation routine does
  291. is to call other routines which will (eventually) generate the code.
  292.  
  293. Writing this sort of code is manageable. In the next layer down, the 32
  294. register x86 gets mapped onto the real, 7 register PII, i.e. register
  295. allocation takes place. Also, some optimization takes place here ---
  296. in the example above, the last mov_l_rr() gets ignored, because its source
  297. and destination are the same. Also, if an immediate is moved into a 
  298. register (and that register thus has a known value, like "src" in the
  299. example), more optimization is possible, and is indeed done. As a result
  300. of that optimization, the superfluous sign extension never actually makes
  301. it into x86 code for this example.
  302. Last but not least, this layer keeps track of what 68k registers need
  303. to be written back to memory to complete the emulated instruction.
  304.  
  305. On the lowest layer, actual bits and bytes are written to memory to encode
  306. raw x86 instructions. If you happen to have an x86-like processor that just
  307. happens to use different instruction encodings, this layer is all you'd need
  308. to change. To actually port to a different target processor, such as a PPC
  309. or StrongArm, you'd have to take care of some more details related to the
  310. way x86 instructions do and don't set flags, but you should still benefit
  311. greatly from the clear separation of layers (hint, hint !-).
  312.  
  313. Each of those layers is fairly complex (and the ^%&$$#$%&*& x86 certainly
  314. doesn't make it any easier; Orthogonality, what's that?), but I can wrap
  315. my mind around each one individually. Trying to do it all in one go would
  316. be far beyond me.
  317.  
  318.  
  319. Using these techniques, it is fairly simple to cover 90+ percent of all
  320. the instructions that get executed in a typical Amiga application, and
  321. the resulting speedup is rather nice.
  322.  
  323.  
  324. Beyond Individual Instructions
  325. ==============================
  326.  
  327. When 90+ percent of instructions are covered, it's fairly likely that
  328. a real translated instruction is followed by another. In that case,
  329. there really isn't much point in writing back all the 68k registers at
  330. the end of the first, only to reload them at the start of the second.
  331. So instead of writing back between any two instructions, the whole
  332. state of the middle layer (which handles register allocation) is simply
  333. kept alive. Of course, we still need to write it all back each time
  334. we fall back on the mov/call method, because those handlers expect
  335. the 68k registers/flags/other state to live in certain places in
  336. memory. That's why it can pay off to generate translation routines for
  337. rarely used instructions --- the average lifetime of a middle-layer
  338. state might increase dramatically. Oh, and of course at the end of a
  339. block everything needs to be written back.
  340.  
  341.  
  342. Memory Access
  343. =============
  344.  
  345. If you do all that, you run up against the last remaining performance killer:
  346. Memory access. Nothing will screw up your performance like the occasional
  347. call to a C function that will not only take several cycles for itself, but
  348. will also potentially clobber all your registers --- meaning you have to 
  349. store it all back before the call, and start from scratch afterwards.
  350.  
  351. The *vast* majority of memory accesses are to "real" memory. The JIT
  352. compiler has two methods for speeding these up.
  353. The first method is simple, safe and a good bit faster than the default
  354. memory access (see above). For this method, instead of having a lookup
  355. table with 65536 pointers to structures that tell us how to get to memory,
  356. we have another table with 65536 values. For memory areas which are 
  357. *not* real memory, this table holds the same info as the other one,
  358. except that the LSB is set. However, for memory areas which *are* 
  359. real memory, this table holds the difference between the address the
  360. x86 sees and the address the emulated Amiga sees. 
  361. So a memory access looks up the content of the appropriate slot in the
  362. table. If the LSB is set, the usual song and dance has to be done. But
  363. if the LSB is clear, we simply add the Amiga address to what we got
  364. from the table, and voila, there is the x86 address of real memory,
  365. from which we can load (or to which we can write) our value, without
  366. calling any C routines, and without clobbering registers. 
  367.  
  368. The other method is much more radical. The Amiga, while it has an address
  369. space of 4GB, only seems to use less than 1.5G of that (From 0x00000000
  370. to 0x60000000). Linux, at least in the 2.4 flavour, allows user programs
  371. pretty much full control over 3GB; But it, too, tends to leave 0x50000000
  372. to 0xbfff0000 unused.
  373. In order to make things fast, we simply map all the real memory from the
  374. Amiga into Linux's address space, with a constant offset. In particular,
  375. the Amiga address X ends up at Linux address 0x50000000+X; So when a 
  376. translation routine wants to generate a memory access at address Y
  377. *and* knows that this access goes to real memory, then it can simply
  378. access the memory at 0x50000000+Y. The magic behind the scenes that is
  379. needed to keep those mappings accurate is a bit complicated, mainly
  380. because the Amiga tends to overmap some areas over and over, but none
  381. of that is really worrysome.
  382. The much bigger problem is "How the heck do we know whether a particular
  383. instruction accesses real mem or I/O?". And here comes the major
  384. hand-waving: We guess! We are making the assumption that any given
  385. instruction will either always access real memory, or always access
  386. I/O. In other words, we expect this to be consistent.
  387. Based on those assumptions, during the normal emulation (i.e. the
  388. data-gathering phase for the compiler), we simply set a flag to 0 
  389. before emulating each instruction; All the I/O memory handlers contain
  390. code that set this flag to non-zero, so if the instruction completes and
  391. the flag is still 0, then that instruction didn't access any I/O. During
  392. the data gathering, the value of this flag is saved after each instruction,
  393. and thus when it comes to compiling, the translation routines know whether
  394. or not the instruction they are translating can use the aggressive method
  395. for memory access.
  396.  
  397. Alas, these assumptions don't always hold. That's why there are many
  398. options for forcing memory access to the slower but safer method. 
  399. If the assumptions don't hold, you will notice --- because there is
  400. no safety net, you *will* get a lovely core dump. (Theoretically,
  401. it would be possible to write a SIGSEGV handler that works out what
  402. the instruction that failed tried to do, and calls the appropriate 
  403. I/O handler; I have done that in the past for the Alpha, but the idea
  404. of trying to decode x86 instructions fills me with dread and fear.
  405. Any takers? ;-). [Update: Hey, that was easier than I imagined --- such
  406. a SIGSEGV handler is now in place, and apparently working just fine. Of
  407. course, it isn't completely general, but instead only handles the
  408. instructions I use for direct memory access....]
  409.  
  410.  
  411.  
  412. Conclusion
  413. ==========
  414.  
  415. UAE can now emulate the CPU at considerably more than 060 speeds. That
  416. should do for a while --- although I am sure that further speedups are
  417. still possible. Patches welcome ;-)
  418.  
  419.